/*
 * Commons.java
 * 
 * Copyright (C) 2009 no.good.at.coding
 * 
 * This file is part of the No Good Commons Library.
 *
 * The No Good Commons Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nogoodatcoding.commons;

import com.mindprod.filetimes.FileTimes;

import java.io.File;
import java.util.List;
import java.util.Random;

/**
 *
 * Common methods that can be used in a variety of projects.
 * 
 * @author no.good.at.coding
 * 
 */
public class Commons
{
    private static final int SMALL_LETTERS_BASE_VALUE = 97; //ASCII value for 'a'
    private static final int CAPITAL_LETTERS_BASE_VALUE = 65; //ASCII value for 'A'
    
    /**
     *
     * Checks the file to see if it is newer than the timestamp.
     * For Windows, the greater of the creation time and 
     * {@link java.io.File#lastModified()} time is compared (in case of any
     * problems with the FileTimes library, the {@code lastModified} timestamp
     * will be compared instead)
     *
     * For non-Windows systems, only the {@code lastModified} timestamp is used.
     * @see com.nogoodatcoding.commons.Commons#getLatestTimestamp(java.io.File)
     *
     * @param fileToCheck The file to check
     * @param timestampToCheckAgainst The timestamp to check against
     * @return {@code true} if the file is newer than timestamp; {@code false}
     *         if older or equal to the timestamp
     *
     */
    public static boolean isNewer(File fileToCheck, long timestampToCheckAgainst)
    {
        if(getLatestTimestamp(fileToCheck) > timestampToCheckAgainst)
            return true;
        else
            return false;
    }

    /**
     *
     * Returns the {@link File#lastModified()} timestamp for the given
     * File; for Windows systems the greatest of the creation time and
     * {@code lastModified} time is returned. In case of any exceptions while
     * trying to read the creation time (using the FileTimes library © Roedy
     * Green/Canadian Mind Products)
     *
     * @param fileToCheck The File for which the timestamp is to be found
     * @return The {@code lastModified} timestamp of the file for non-Windows
     *         systems and the greater of the creation time and
     *         {@code lastModified} time for Windows systems
     *
     */
    public static long getLatestTimestamp(File fileToCheck)
    {
        //check if it's a Windows machine
        if(System.getProperty("os.name").indexOf("Win")!=-1)
        {
            try
            {
                long creationTime = FileTimes.getFileCreated(fileToCheck.getPath());
                long lastModifiedTime = fileToCheck.lastModified();

                //return the greater of the two
                return lastModifiedTime>creationTime?lastModifiedTime:creationTime;
            }
            catch(Error e)
            {
                //swallow error to avoid breaking the caller
                System.out.println("There was an ERROR caught; this probably means you do not have the required nativefiletimes.dll in the library path");
                //e.printStackTrace();
            }
        }

        //default to returning the lastModified timestamp for non-Windows platforms
        //and in case of any exceptions for Windows
        return fileToCheck.lastModified();

    }

    /**
     *
     * Trims the list in-place (the List passed will be modified) to contain
     * {@code itemsRequired} number of items.
     * No-op if the list length is lesser than or equal to {@code itemsRequired}
     * 
     * @param listToBeTrimmed The List to be trimmed
     * @param itemsRequired The number of items required to be left in
     *                      {@code listToBeTrimmed}
     *
     */
    public static void trim(List listToBeTrimmed, int itemsRequired)
    {
        boolean isTrimmed = false;
        while(listToBeTrimmed.size() > itemsRequired)
        {
            listToBeTrimmed.remove(listToBeTrimmed.size()-1);
            isTrimmed = true;
        }

        //request GC since the list has been trimmed
        //there are possibly many objects waiting
        //to be GC'd
        if(isTrimmed)
            System.gc();
    }

    /**
     *
     * Generates a (temporary) password the contents of which are decided by the
     * various parameters. If {@code hasSpecialChars} is {@code false}, this is
     * ignored. If {@code hasSpecialChars} is {@code true} and the passed array
     * of special characters is either null or the length is 0, default special
     * characters {@code '!', '#', '$', '%', '&', '*'} will be used.
     *
     * @param passwordLength The required password length to be generated
     * @param hasSmallLetters Whether the generated password should contain
     *                        small case letters
     * @param hasCaps Whether the generated password should contain capital
     *                letters
     * @param hasDigits Whether the generated password should contain digits
     * @param hasSpecialChars Whether the generated password should contain
     *                        special characters; picked from the array of
     *                        allowable special characters
     * @param allowedSpecialCharacters A list of special characters that are
     *                                 allowed and will be picked from while
     *                                 generating the password
     *
     * @return The String containing the password generated with contents and
     *         length as per parameters passed
     *
     * @throws IllegalArgumentException If no option has been selected from
     *                                  among {@code hasSmallLetter},
     *                                  {@code hasCaps}, {@code hasDigits},
     *                                  {@code hasSpecialChars} or if
     *                                  {@code passwordLength} is negative
     *
     */
    public static String generateTempPassword( int passwordLength, boolean hasSmallLetters, boolean hasCaps, boolean hasDigits, boolean hasSpecialChars, char [] allowedSpecialCharacters)
        throws IllegalArgumentException
    {

        ////////////////////////////////////////////////////////////CHECK FOR VALID PARAMETERS////////////////////////////////////////////////////////////
        if ( !hasSmallLetters & !hasCaps & !hasDigits & !hasSpecialChars ) //check that atleast one boolean is true
        {
            throw new IllegalArgumentException("Atleast one selector for password contents must be true");
        }

        if ( passwordLength <= 0 ) //check if length is valid
        {
            throw new IllegalArgumentException("Password length cannot be negative");
        }

        if ( hasSpecialChars & ( allowedSpecialCharacters == null || allowedSpecialCharacters.length == 0) )
            allowedSpecialCharacters = new char [] {'!', '#', '$', '%', '&', '*'};//use default values for special characters

        ////////////////////////////////////////////////////////////END CHECK FOR VALID PARAMETERS////////////////////////////////////////////////////////////

        ////////////////////////////////////////////////////////////INITIALISE VARIABLES////////////////////////////////////////////////////////////
        StringBuilder password = new StringBuilder("");
        int currentValue = 0; //to hold currently generated random value

        Random randomGenerator = new Random();
        ////////////////////////////////////////////////////////////END INITIALISE VARIABLES////////////////////////////////////////////////////////////


        ////////////////////////////////////////////////////////////LOOP////////////////////////////////////////////////////////////
        //to create a string of specified length
        for ( int i = 0; password.length() <= passwordLength; i++ )
        {
            ////////////////////////////////////////////////////////////SWITCH////////////////////////////////////////////////////////////
            //to randomly decide whether current character to be added should be a small case, cap, digit or special char
            switch ( (int) (Math.random()*10))
            {
            //Cases to generate small case letters, more cases than digits and special chars to get more occurences
            case 0:
            case 1:
            case 2:
            case 3:
                if ( !hasSmallLetters )
                    continue; //if this was set to be not included then continue

    //			System.out.println("=======0-2========");
                currentValue = randomGenerator.nextInt(26);
    //			System.out.println(currentValue);


                currentValue += SMALL_LETTERS_BASE_VALUE;//convert to valid ASCII for a-z

                password.append((char)currentValue);
    //			System.out.println((char)currentValue);

                break;

            //Cases to generate capital letters, more cases than digits and special chars to get more occurences
            case 4:
            case 5:
            case 6:
            case 7:
                if ( !hasCaps )
                    continue;//if this was set to be not included then continue

    //			System.out.println("=======3-5========");
                    currentValue = randomGenerator.nextInt(26);
    //			System.out.println(currentValue);

                currentValue += CAPITAL_LETTERS_BASE_VALUE;//convert to valid ASCII for A-Z
    //			System.out.println(currentValue);

                password.append((char)currentValue);

    //			System.out.println((char)currentValue);

                break;
            //Cases for digits, only 1 case to give lower probability and fewer occurences
            case 8:
                if ( !hasDigits )
                    continue;//if this was set to be not included then continue

    //			System.out.println("=======6-7========");
                    currentValue = randomGenerator.nextInt(10);
    //			System.out.println(currentValue);

                password.append(currentValue);

    //			System.out.println(currentValue);


                //use the value as is since it's already a numeral
                break;

            //Cases for special characters, only 1 case to give lower probability and fewer occurences
            case 9:
                if ( !hasSpecialChars )
                    continue;//if this was set to be not included then continue

    //			System.out.println("=======8-9========");
                    currentValue = randomGenerator.nextInt(allowedSpecialCharacters.length);
    //			System.out.println(currentValue);

                password.append(allowedSpecialCharacters[currentValue]);
    //			System.out.println(currentValue);


                //use the value as is since it's already a numeral
                break;

            }//end of switch
            ////////////////////////////////////////////////////////////END SWITCH////////////////////////////////////////////////////////////
        }//end of for
        ////////////////////////////////////////////////////////////END LOOP////////////////////////////////////////////////////////////

    //	System.out.println(password);
        return password.toString();
    }//end of generateTempPassword()
}
